﻿HmJS.$Import('core.fx');

/*
---
* name: Fx.CSS3
* version: 2.1-0
* script: Fx.CSS3.js
* license: MIT-style license.
* description: Provides the CSS3 implementations of Tween, Morph and Elements
* copyright: Copyright (c) 2010, Dipl.-Ing. (FH) André Fiedler <kontakt at visualdrugs dot net>, based on code by eskimoblood (mootools users group)
* copyright: Copyright (c) 2011 Fred Cox mcfedr@gmail.com
* download: http://mootools.net/forge/p/fx_css3
* source: http://github.com/mcfedr/mootools-fx-css3
*
* authors: [Fred Cox, André Fiedler, eskimoblood, 苦苦的苦瓜]
*
* requires: [Core/Class.Extras, Core/Element.Event, Core/Element.Style, Core/Fx, Core/Fx.Tween, Core/Fx.Morph, More/Array.Extras, More/Fx.Elements]
* 
* provides: [Fx.Tween.CSS3, Fx.Morph.CSS3, Fx.Elements.CSS3]
* 
* 修改:
*		[2011-09-25] mootools内核升级到1.4
...
*/

/*
---
name: Fx.Tween.CSS3
version: 1.0.2-1
script: Fx.Tween.CSS3.js
license: MIT-style license.
description: Same behavior like Fx.Tween but tries to use native CSS3 transition if possible.
copyright: Copyright (c) 2010, Dipl.-Ing. (FH) André Fiedler <kontakt at visualdrugs dot net>, based on code by eskimoblood (mootools users group)
download: http://mootools.net/forge/p/fx_tween_css3
source: http://github.com/SunboX/mootools-fx-tween-css3
htmltags: http://www.w3schools.com/html5/html5_reference.asp

authors: [André Fiedler, eskimoblood](http://visualdrugs.net)

requires: [Core/Class.Extras, Core/Element.Event, Core/Element.Style, Core/Fx.Tween]

provides: [Fx.Tween.CSS3]
...
*/

HmJS.register('base.fx.css3', function ($ns) {

	var Fx = this.Fx;

	// #region -- Object.isEqual --

	Object.extend({

		isEqual: function (a, b) {
			// Check object identity.
			if (a === b) return true;
			// Different types?
			var atype = typeOf(a),
					btype = typeOf(b);
			if (atype != btype) { return false; }
			// Basic equality test (watch out for coercions).
			if (a == b) { return true; }
			// One is falsy and the other truthy.
			if ((!a && b) || (a && !b)) { return false; }
			// One of them implements an isEqual()?
			if (a.isEqual) { return a.isEqual(b); }
			if (b.isEqual) { return b.isEqual(a); }
			// Both are NaN?
			if ((a !== a) && (b !== b)) { return false; }
			// Check dates' integer values.
			if (atype == 'date') { return a.getTime() === b.getTime(); }
			if (atype == 'function') { return true; }
			// Compare regular expressions.
			if (atype == 'regexp') {
				return a.source === b.source
						&& a.global === b.global
						&& a.ignoreCase === b.ignoreCase
						&& a.multiline === b.multiline;
			}
			// If a is not an object by this point, we can't handle it.
			if (atype !== 'object' && atype !== 'array') { return false; }
			// Check for different array lengths before comparing contents.
			if (a.length && (a.length !== b.length)) { return false; }
			// Nothing else worked, deep compare the contents.
			var aKeys = Object.keys(a),
					bKeys = Object.keys(b);
			// Different object sizes?
			if (aKeys.length != bKeys.length) { return false; }
			// Recursive comparison of contents.
			for (var key in a) {
				if (!(key in b) || !Object.isEqual(a[key], b[key])) {
					return false;
				}
			}
			return true;
		}

	});

	// #endregion

	// #region -- Private --

	var css3Features = (function () {

		var prefix = (function () {
			var prefixes = ['ms', 'O', 'Moz', 'webkit'],
					style = document.documentElement.style;
			for (var l = prefixes.length; l--; ) {
				var prefix = prefixes[l];
				if (typeof style[prefix + 'Transition'] != 'undefined') {
					return prefix;
				}
			}
			return false;
		})();

		if (prefix) {
			return {
				transition: prefix + 'Transition',
				transitionProperty: prefix + 'TransitionProperty',
				transitionDuration: prefix + 'TransitionDuration',
				transitionTimingFunction: prefix + 'TransitionTimingFunction',
				transitionend: (prefix == 'ms' || prefix == 'Moz') ? 'transitionEnd' : prefix + 'TransitionEnd'
			}
		}
		return false;
	})();

	Element.NativeEvents[css3Features.transitionend] = 2;

	DOMEvent.implement({

		getPropertyName: function () {
			return this.event.propertyName;
		},

		getElapsedTime: function (nativeTime) {
			return nativeTime ? this.event.elapsedTime : (this.event.elapsedTime * 1000).toInt();
		}

	});

	Array.implement({

		containsArray: function (array) {
			return array.every(function (v) {
				return this.contains(v);
			}, this);
		}

	});

	var transitionTimings = {
		'linear': '0,0,1,1',
		'expo:in': '0.71,0.01,0.83,0',
		'expo:out': '0.14,1,0.32,0.99',
		'expo:in:out': '0.85,0,0.15,1',
		'circ:in': '0.34,0,0.96,0.23',
		'circ:out': '0,0.5,0.37,0.98',
		'circ:in:out': '0.88,0.1,0.12,0.9',
		'sine:in': '0.22,0.04,0.36,0',
		'sine:out': '0.04,0,0.5,1',
		'sine:in:out': '0.37,0.01,0.63,1',
		'quad:in': '0.14,0.01,0.49,0',
		'quad:out': '0.01,0,0.43,1',
		'quad:in:out': '0.47,0.04,0.53,0.96',
		'cubic:in': '0.35,0,0.65,0',
		'cubic:out': '0.09,0.25,0.24,1',
		'cubic:in:out': '0.66,0,0.34,1',
		'quart:in': '0.69,0,0.76,0.17',
		'quart:out': '0.26,0.96,0.44,1',
		'quart:in:out': '0.76,0,0.24,1',
		'quint:in': '0.64,0,0.78,0',
		'quint:out': '0.22,1,0.35,1',
		'quint:in:out': '0.9,0,0.1,1'
	};

	var animatable = ['background-color', 'border-bottom-width', 'border-left-width', 'border-right-width',
				'border-spacing', 'border-top-width', 'border-width', 'bottom', 'color', 'font-size', 'font-weight',
				'height', 'left', 'letter-spacing', 'line-height', 'margin-bottom', 'margin-left', 'margin-right',
				'margin-top', 'max-height', 'max-width', 'min-height', 'min-width', 'opacity', 'outline-color', 'outline-offset',
				'outline-width', 'padding-bottom', 'padding-left', 'padding-right', 'padding-top', 'right', 'text-indent',
				'top', 'vertical-align', 'visibility', 'width', 'word-spacing', 'z-index'];

	// #endregion

	/*
	This is a list of properties listed by w3c but that are currently largely unsupported
	http://www.w3.org/TR/css3-transitions/#animatable-properties-
	http://www.opera.com/docs/specs/presto29/css/transitions/
	
	var unsupported = ['background-image', 'background-position', 'border-bottom-color', 'border-color', 'border-left-color',
	'border-right-color', 'border-top-color', 'crop', 'grid-*', 'text-shadow', 'zoom']
	*/

	// #region -- Fx.CSS3Funcs --

	var CSS3Funcs = Fx.CSS3Funcs = {

		initialize: function (element, options) {
			if (css3Features) {
				options = options || {};
				if (!options.transition || !transitionTimings[options.transition]) {
					options.transition = 'sine:in:out';
				}
				if (this.initializeCSS3) {
					this.initializeCSS3(element, options);
				}
			}
			this.parent(element, options);
		},

		startCSS3: function (properties, from, to) {
			/*if(!this.check()) return this;*/
			if (!this.check(properties, from, to)) { return this; }

			if (!Object.isEqual(from, to)) {
				var el = this.element;

				this.preTransStyles = el.getStyles(
						css3Features.transitionProperty,
						css3Features.transitionDuration,
						css3Features.transitionTimingFunction);

				var incomplete = {};
				properties.each(function (p) {
					incomplete[p] = false;
				});

				this.animatingProperties = properties;

				this.boundComplete = function (e) {
					incomplete[e.getPropertyName()] = true;
					if (Object.every(incomplete, function (v) { return v; })) {
						el.removeEvent(css3Features.transitionend, this.boundComplete);
						el.setStyles(this.preTransStyles);
						this.boundComplete = null;
						this.fireEvent('complete', this.subject);
						if (!this.callChain()) {
							this.fireEvent('chainComplete', this.subject);
						}
					}
				} .bind(this);

				el.addEvent(css3Features.transitionend, this.boundComplete);

				var trans = function () {
					var transStyles = {};
					transStyles[css3Features.transitionProperty] = properties.reduce(function (a, b) { return a + ', ' + b; });
					transStyles[css3Features.transitionDuration] = this.options.duration + 'ms';
					transStyles[css3Features.transitionTimingFunction] = 'cubic-bezier(' + transitionTimings[this.options.transition] + ')';
					el.setStyles(transStyles);
					this.set(this.compute(from, to, 1));
				} .bind(this);

				el.setStyle(css3Features.transitionProperty, 'none');
				this.set(this.compute(from, to, 0));
				trans.delay(0.1);
				this.fireEvent('start', this.subject);
			} else {
				this.fireEvent('start', this.subject);
				this.fireEvent('complete', this.subject);
			}
			return this;
		} /*,

		pause: function () {
			if (this.css3Supported) {
				return this;
			}
			return this.parent();
		},

		resume: function () {
			if (this.css3Supported) {
				return this;
			}
			return this.parent();
		},

		isRunning: function () {
			if (this.css3Supported) {
				return !!this.boundComplete;
			}
			return this.parent();
		}*/

	};

	CSS3Funcs.css3Features = css3Features;
	CSS3Funcs.transitionTimings = transitionTimings;
	CSS3Funcs.animatable = animatable;

	// #endregion

	// #region -- Fx.CSS3Stop --

	var CSS3Stop = Fx.CSS3Stop = {

		cancel: function () {
			if (this.css3Supported) {
				if (this.isRunning()) {
					var el = this.element;
					el.removeEvent(css3Features.transitionend, this.boundComplete);
					el.setStyles(this.preTransStyles);
					this.boundComplete = null;
				}
				/*return this;*/
			}
			return this.parent();
		},

		stop: function () {
			if (this.css3Supported) {
				if (this.isRunning()) {
					var el = this.element;
					el.removeEvent(css3Features.transitionend, this.boundComplete);
					el.setStyles(this.preTransStyles);
					this.boundComplete = null;
				}
				/*return this;*/
			}
			return this.parent();
		}

	};

	// #endregion

	// #region -- Fx.Tween.CSS3 --

	var tweenCSS3 = Fx.Tween.CSS3 = new Class({

		Extends: Fx.Tween,

		checkCSS3: function (property) {
			return (css3Features && animatable.contains(property));
		},

		start: function (property, from, to) {
			var args = Array.flatten(arguments);
			this.property = this.options.property || args.shift();
			if ((this.css3Supported = this.checkCSS3(this.property))) {
				var parsed = this.prepare(this.element, this.property, args);
				return this.startCSS3([this.property], parsed.from, parsed.to);
			}
			return this.parent(property, from, to);
		}
	});

	tweenCSS3.implement(CSS3Funcs);
	tweenCSS3.implement(CSS3Stop);

	// #endregion

	// #region -- Fx.Morph.CSS3 --

	var morphCSS3 = Fx.Morph.CSS3 = new Class({

		Extends: Fx.Morph,

		checkCSS3: function (properties) {
			return (css3Features && animatable.containsArray(Object.keys(properties)));
		},

		start: function (properties) {
			if ((this.css3Supported = this.checkCSS3(properties))) {
				if (typeof properties == 'string') {
					properties = this.search(properties);
				}
				var from = {},
						to = {},
						usedProps = [];
				for (var p in properties) {
					var parsed = this.prepare(this.element, p, properties[p]);
					if (!Object.isEqual(parsed.from, parsed.to)) {
						from[p] = parsed.from;
						to[p] = parsed.to;
						usedProps.push(p);
					}
				}
				return this.startCSS3(usedProps, from, to);
			}
			return this.parent(properties);
		}
	});

	morphCSS3.implement(CSS3Funcs);
	morphCSS3.implement(CSS3Stop);

	// #endregion

	// #region -- Fx.Elements.CSS3 --

	var elementsCSS3 = Fx.Elements.CSS3 = new Class({

		Extends: Fx.Elements,

		initializeCSS3: function (elements, options) {
			this.morphers = elements.map(function (element) {
				return new morphCSS3(element, Object.merge({}, options));
			} .bind(this));
		},

		//		initializeCSS3: function (elements, options) {
		//			this.morphers = elements.map(function (element) {
		//				return new morphCSS3(element, Object.merge({}, options, {
		//					onComplete: this._complete.bind(this),
		//					onCancel: this._cancel.bind(this)
		//				}));
		//			} .bind(this));
		//		},

		//		_complete: function () {
		//			if (this.count-- == 0) {
		//				this.fireEvent('complete', this.subject);
		//				if (!this.callChain()) {
		//					this.fireEvent('chainComplete', this.subject);
		//				}
		//			}
		//		},

		//		_cancel: function () {
		//			if (this.count-- == 0) {
		//				this.fireEvent('cancel', this.subject);
		//				this.clearChain();
		//			}
		//		},

		checkCSS3: function (obj) {
			return (css3Features && Object.every(obj, function (properties, key) {
				if (properties && this.elements[key]) {
					return animatable.containsArray(Object.keys(properties));
				}
				return true;
			}, this));
		},

		count: 0,

		start: function (obj) {
			if ((this.css3Supported = this.checkCSS3(obj))) {
				if (!this.check()) { return this; }
				this.count = 0;

				Object.each(obj, function (properties, key) {
					if (properties && this.elements[key]) {
						this.count++;
						this.morphers[key].start(properties);
					}
				}, this);

				this.fireEvent('start', this);
				return this;
			}
			return this.parent(obj);
		} /*,

		stop: function () {
			if (this.css3Supported) {
				Object.each(this.morphers, function (morph) {
					morph.stop();
				});
				return this;
			}
			return this.parent();
		},

		cancel: function () {
			if (this.css3Supported) {
				Object.each(this.morphers, function (morph) {
					morph.cancel();
				});
				return this;
			}
			return this.parent();
		},

		isRunning: function () {
			if (this.css3Supported) {
				return this.count != 0;
			}
			return this.parent();
		}*/

	});

	elementsCSS3.implement(CSS3Funcs);

	// #endregion

});
